#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "cJSON.h"
#include "hrd_core.h"
#include "hrd_coord.h"
#include "hrd_coordinate_list_loader.h"
#include "hrd_bitmask.h"
#include "hrd_logger.h"

const char *ACTION_STRING_TABLE[4] = {
    "UP", "DOWN", "LEFT", "RIGHT"
};

const hrd_object_allowance_t ALL_ALLOWED_OBJECT_ALLOWANCE = {
    .allowed_directions_number = 4,
    .allowed_directions = {
        [0] = MOVE_UP, [1] = MOVE_DOWN, [2] = MOVE_LEFT, [3] = MOVE_RIGHT
    }
};


static hrd_bitmask_t loadHrdObjectBitmask(cJSON *hrd_object_json)
{
    int16_t coordinate_number = 0;
    struct coordinate_s coordinate_list[MAX_OBJECT_NUMBER] = {0};
    cJSON *coordinate_json = NULL;

    coordinate_json = cJSON_GetObjectItemCaseSensitive(hrd_object_json, "coordinate");
    if (coordinate_json == NULL) {
        LOG_FATAL("Unable to get coordinate.");
        return 0;
    }

    coordinate_number = HrdCoordinateListLoader_Load(coordinate_json, coordinate_list, MAX_OBJECT_NUMBER);
    if (coordinate_number == LOAD_LIST_FAILURE) {
        LOG_FATAL("Error occur while loading goal coordinates.");
        return 0;
    }

    return HrdCoord_CoordinateListToBitmask(coordinate_list, coordinate_number);
}


static bool isStringInTable(char *string, const char *string_table[], uint8_t table_size)
{
    uint8_t i;
    for (i = 0; i < table_size; i++) {
        if (strcmp(string_table[i], string) == 0) {
            return true;
        }
    }

    return false;
}


static hrd_object_move_direction_et convertActionDirectionStringToEnum(char *action_direction_string)
{
    switch (action_direction_string[0]) {
        case 'U':
            return MOVE_UP;
        case 'D':
            return MOVE_DOWN;
        case 'L':
            return MOVE_LEFT;
        case 'R':
            return MOVE_RIGHT;
    }
}


#define CHECK_ALLOWED_ACTION(allowed_action_json) \
do { \
    if (!cJSON_IsString(allowed_action_json)) { \
        LOG_WARNING("Not a string occur in allowed actions. Consider it allow all actions."); \
        return ALL_ALLOWED_OBJECT_ALLOWANCE; \
    } \
    if (!isStringInTable(allowed_action_json->valuestring, ACTION_STRING_TABLE, 4)) { \
        LOG_WARNING("\"%s\" action not supported. Consider it allow all actions.", allowed_action_json->valuestring); \
        return ALL_ALLOWED_OBJECT_ALLOWANCE; \
    } \
} while(0)


static hrd_object_allowance_t loadHrdObjectAllowance(cJSON *hrd_object_json)
{
    uint8_t index;
    cJSON *allowed_directions_json = NULL;
    cJSON *allowed_action_json = NULL;
    hrd_object_allowance_t object_allowance = {0};

    allowed_directions_json = cJSON_GetObjectItemCaseSensitive(hrd_object_json, "allowed_directions");
    if (allowed_directions_json == NULL) {
        LOG_WARNING("Unable to get allowed_directions. Consider it allow all actions.");
        return ALL_ALLOWED_OBJECT_ALLOWANCE;
    }

    object_allowance.allowed_directions_number = cJSON_GetArraySize(allowed_directions_json);
    if (object_allowance.allowed_directions_number > MAX_MOVEMENT_NUMBER) {
        LOG_WARNING("Too many allow actions, MAX %d supported. Consider it allow all actions.", MAX_MOVEMENT_NUMBER);
        return ALL_ALLOWED_OBJECT_ALLOWANCE;
    }
    if (object_allowance.allowed_directions_number < 1) {
        LOG_WARNING("Too less allow actions. Consider it allow all actions.");
        return ALL_ALLOWED_OBJECT_ALLOWANCE;
    }

    index = 0;
    cJSON_ArrayForEach(allowed_action_json, allowed_directions_json) {
        CHECK_ALLOWED_ACTION(allowed_action_json);
        object_allowance.allowed_directions[index++] = convertActionDirectionStringToEnum(allowed_action_json->valuestring);
    }

    return object_allowance;
}


hrd_problem_t *HrdObjectListLoader_Load(cJSON *base_json, hrd_problem_t *hrd_problem)
{
    int index;
    int hrd_object_number = 0;
    cJSON *hrd_object_json = NULL;
    cJSON *hrd_object_list_json = NULL;
    hrd_bitmask_list_t hrd_bitmask_list = {0};
    hrd_object_allowance_t object_allowance_list[MAX_OBJECT_NUMBER];

    hrd_object_list_json = cJSON_GetObjectItemCaseSensitive(base_json, "object_list");
    if (hrd_object_list_json == NULL) {
        LOG_FATAL("Unable to get object_list.");
        return NULL;
    }

    hrd_object_number = cJSON_GetArraySize(hrd_object_list_json);
    if (hrd_object_number > MAX_OBJECT_NUMBER) {
        LOG_WARNING("Too many objects to load in file. Only %d will be loaded.", MAX_OBJECT_NUMBER);
        hrd_object_number = MAX_OBJECT_NUMBER;
    }

    index = 1;
    hrd_bitmask_list[MAP_BITMASK_INDEX] = hrd_problem->bitmask_list[MAP_BITMASK_INDEX];
    cJSON_ArrayForEach(hrd_object_json, hrd_object_list_json) {
        if (index >= MAX_OBJECT_NUMBER) {
            break;
        }

        hrd_bitmask_list[index] = loadHrdObjectBitmask(hrd_object_json);
        object_allowance_list[index] = loadHrdObjectAllowance(hrd_object_json);

        if (hrd_bitmask_list[index] == 0) {
            LOG_WARNING("%d-th object has no size. Skipped.", index);
            continue;
        }

        index++;
    }

    if (HrdBitmask_IsBitmaskListValid(hrd_bitmask_list) == false) {
        LOG_FATAL("Overlap detected.");
        return NULL;
    }

    hrd_problem->object_number = index - 1;
    memcpy(hrd_problem->object_allowance_list, object_allowance_list, sizeof(object_allowance_list));
    memcpy(hrd_problem->bitmask_list, hrd_bitmask_list, sizeof(hrd_bitmask_list_t));

    return hrd_problem;
}